#include "p24FJ256GB110.h"
#include "Mikro.h"
#include "MikroSPI.h"
#include "MikroFlash.h"

#define M25P80_CS (_LATC2)

/* Instruction Set */
#define M25P80_WREN      (0b00000110) // write enable
#define M25P80_WRDI      (0b00000100) // write disable
#define M25P80_RDSR      (0b00000101) // read status register
#define M25P80_WRSR      (0b00000001) // write status register
#define M25P80_READ      (0b00000011) // read data bytes
#define M25P80_FAST_READ (0b00001011) // fast read data bytes
#define M25P80_PP        (0b00000010) // page program
#define M25P80_SE        (0b11011000) // sector erase
#define M25P80_BE        (0b11000111) // bulk erase
#define M25P80_DP        (0b10111001) // deep power-down
#define M25P80_RES       (0b10101011) // release from deep power-down; read electronic signature

/* Status register bitmasks */
#define M25P80_SR_SRWD        (0b10000000) // status register write protect
#define M25P80_SR_BP2         (0b00010000) // block protected bit 2
#define M25P80_SR_BP1         (0b00001000) // block protected bit 1
#define M25P80_SR_BP0         (0b00000100) // block protected bit 0
#define M25P80_SR_WEL         (0b00000010) // write enabled latch bit
#define M25P80_SR_WIP         (0b00000001) // write in progress bit
#define M25P80_SR_DEFAULT     (0b00000000) // no protection mode

// Chip select logic
#define CS_DESELECT (1)
#define CS_SELECT (0)

void write_enable();
void write_status_register(uint8 SR_val);
void wait_for_WIP();
void wait_for_WEL();

/* Initialize the M25P80 flash memory device */
void M25P80_Init(void)
{
   // set up chip select
   _TRISC2 = 0;
   _LATC2 = CS_DESELECT;

   // settle clock at correct idle level with a dummy write
   SPI2_Write(0xff);

   // disable protection mode
   write_status_register(M25P80_SR_DEFAULT); 
}

/* Erase entire memory (set all bits to 1). (TAKES AROUND 10 SECONDS)
*
* The Bulk Erase (BE) instruction is executed only if all Block Protect
* (BP2, BP1, BP0) bits are 0. The Bulk Erase (BE) instruction is ignored if
* one, or more, sectors are protected.
*/
void M25P80_bulk_erase()
{
   write_enable();

   // Bulk Erase sequence
   M25P80_CS = CS_SELECT;
   SPI2_Write(M25P80_BE);
   M25P80_CS = CS_DESELECT;

   // Wait for bulk erase to finish
   M25P80_CS = CS_SELECT;
   wait_for_WIP();
   M25P80_CS = CS_DESELECT;
}

/* Erase an entire sector (set all bits to 1). */ 
void M25P80_sector_erase(uint8 sectorNumber)
{
	if ( sectorNumber > M25P80_TOTAL_SECTORS ) {
		return; // invalid sector number
	}

   write_enable();

   // Sector Erase sequence
   M25P80_CS = CS_SELECT;
   SPI2_Write(M25P80_SE);
      // serialize sector address
   SPI2_Write(sectorNumber); 
   SPI2_Write(0);
   SPI2_Write(0);
   M25P80_CS = CS_DESELECT;

   // Wait for WIP unset
   M25P80_CS = CS_SELECT;
   wait_for_WIP();
   M25P80_CS = CS_DESELECT;
}

/* Program num_bytes bytes (up to 256 bytes) to M25P80 memory starting at address pageAddr
 * using the data in write_data.
 *
 * Precede this function by clearing the page being written to (via M25P80_sector_erase() or
 * M25P80_bulk_erase()) because bits are cleared only to 1 and written only to 0.
 */
void M25P80_page_program(uint32 pageAddr, uint16 num_bytes, uint8 *write_data)
{
   if( num_bytes > M25P80_BYTES_PER_PAGE ) {
      return; // cannot write more than one page at a time; return safely.
   }

   uint32 byteAddr = pageAddr * M25P80_BYTES_PER_PAGE;
   uint16 i;

   write_enable();

   // Page Program sequence
   M25P80_CS = CS_SELECT;
   SPI2_Write(M25P80_PP);
      // Serialize and send address
   SPI2_Write( (uint8)((byteAddr >> 16) & 0x0f) );
   SPI2_Write( (uint8)((byteAddr >> 8) & 0x0f) );
   SPI2_Write( (uint8)(byteAddr & 0x0f) );
   for (i = 0; i < num_bytes; i++) {
      SPI2_Write( write_data[i] );
   }
   M25P80_CS = CS_DESELECT;

   M25P80_CS = CS_SELECT;
   wait_for_WIP();
   M25P80_CS = CS_DESELECT;
}

/* Read num_bytes bytes from M25P80 memory starting at memory location addr (which
 * is not limited to a page boundary) and store it in array read_data.
 * 
 * When the highest address is reached, the address counter rolls
 * over to 000000h, allowing the read sequence to be continued indefinitely.
 */
void M25P80_read(uint32 addr, uint16 num_bytes, uint8 *read_data)
{
   // TODO bounds-checking
   int i;

   M25P80_CS = CS_SELECT;
   SPI2_Write(M25P80_READ);
      // serialize address
   SPI2_Write( (uint8)((addr>>16) & 0x0f) );
   SPI2_Write( (uint8)((addr>>8) & 0x0f) );
   SPI2_Write( (uint8)(addr & 0x0f) );
   for ( i = 0; i < num_bytes; i++ ) {
      read_data[i] = SPI2_Write(0xff);
   }
   M25P80_CS = CS_DESELECT; // break READ sequence
}

/* Set the M25P80 status register WEL bit to program or erase */
void write_enable()
{
   // Write enable sequence
   M25P80_CS = CS_SELECT;
   SPI2_Write(M25P80_WREN);
   M25P80_CS = CS_DESELECT;
   M25P80_CS = CS_SELECT;
   wait_for_WIP();
   wait_for_WEL();
   M25P80_CS = CS_DESELECT;
}

/* Write a value to the M25P80 status register. */
void write_status_register(uint8 SR_val)
{
   write_enable();
   
   // Write Status Register sequence
   M25P80_CS = CS_SELECT;
   SPI2_Write(M25P80_WRSR);
   SPI2_Write(SR_val);
   M25P80_CS = CS_DESELECT;

   // Wait for status register to finish being written
   M25P80_CS = CS_SELECT;
   wait_for_WIP();
   M25P80_CS = CS_DESELECT;
}

/* Wait for M25P80 Write In Progress bit to be unset */
void wait_for_WIP()
{
   uint8 statusReg;
   SPI2_Write(M25P80_RDSR);
   do {
      statusReg = SPI2_Write(0xff);
   } while ( statusReg & M25P80_SR_WIP );
}

/* Wait for M25P80 Write Enable latch to be set */
void wait_for_WEL()
{
   uint8 statusReg;
   SPI2_Write(M25P80_RDSR);
   do {
      statusReg = SPI2_Write(0xff);
   } while ( !(statusReg & M25P80_SR_WEL) );
}

// TODO M25P80_fast_read()

